﻿// Copyright 2010 Maxime Serrano <lastchronicler@gmail.com>
using System;
using System.Net;

namespace MCSharp {
    static class StandardCommands {
        internal static void Init() {
            CommandList.RegisterCommand(cdHome);
            CommandList.RegisterCommand(cdSetHome);

            CommandList.RegisterCommand(cdTP);
            CommandList.RegisterCommand(cdBring);

            CommandList.RegisterCommand(cdKick);
            CommandList.RegisterCommand(cdChangeClass);

            CommandList.RegisterCommand(cdItem);

            CommandList.RegisterCommand(cdAddToWhitelist);
        }

        #region Home

        static CommandDescriptor cdHome = new CommandDescriptor {
            name = "home",
            consoleSafe = true,
            usage = "/home",
            help = "Teleports you to home (spawn).",
            handler = cmdGoHome
        };

        internal static void cmdGoHome(Player player, Command cmd) {
            player.Send(PacketWriter.MakePositionAndLook(player.homePos));
        }

        static CommandDescriptor cdSetHome = new CommandDescriptor {
            name = "sethome",
            consoleSafe = true,
            usage = "/sethome",
            help = "Sets your home position.",
            handler = cmdSetHome
        };

        internal static void cmdSetHome(Player player, Command cmd) {
            player.homePos = player.pos;
            player.homePos.y += 2;
            player.Message(Color.Announcement + "For the rest of this session, your /home position will be here.");
        }

        #endregion

        #region Teleportation

        static CommandDescriptor cdTP = new CommandDescriptor {
            name = "tp",
            permissions = new Permission[] { Permission.Teleport },
            usage = "/tp [PlayerName / X Y Z]",
            help = "Teleports you to a specified player's location, or the coordinates your specify.",
            handler = TP
        };

        internal static void TP(Player player, Command cmd) {
            string name = cmd.Next();
            if (name != null) {
                Player target = Server.FindPlayer(name);
                if (target != null) {
                    Position pos = target.pos;
                    pos.y += 2;
                    player.Send(PacketWriter.MakePositionAndLook(pos));
                } else {
                    cmd.Rewind();
                    int x, y, z;
                    if (int.TryParse(cmd.Next(), out x) && int.TryParse(cmd.Next(), out y) && int.TryParse(cmd.Next(), out z)) {
                        if (y < 0 || y > 128) {
                            player.Message(Color.Announcement + "Invalid Y coordinate");
                            return;
                        }
                        player.Send(PacketWriter.MakePositionAndLook(new Position((double)x, (double)y, (double)z)));
                    } else
                        player.NoPlayerMessage(name);
                }
            }
        }

        static CommandDescriptor cdBring = new CommandDescriptor {
            name = "bring",
            permissions = new Permission[] { Permission.Bring },
            usage = "/bring PlayerName",
            help = "Teleports a player to you.",
            handler = Bring
        };

        internal static void Bring(Player player, Command cmd) {
            string name = cmd.Next();
            if (name == null) {
                player.Message(Color.Announcement + "You must specify a player name!");
                return;
            }
            Player target = Server.FindPlayer(name);
            if (target == null) {
                player.NoPlayerMessage(name);
            } else {
                Position pos = player.pos;
                pos.y += 2;
                target.Send(PacketWriter.MakePositionAndLook(pos));
            }
        }

        #endregion

        #region Kick / Ban

        static CommandDescriptor cdKick = new CommandDescriptor {
            name = "kick",
            aliases = new string[] { "k" },
            consoleSafe = true,
            permissions = new Permission[] { Permission.Kick },
            usage = "/kick PlayerName [Message]",
            help = "Kicks the specified player from the server. " +
                   "Kicked player gets to see the specified message on their disconnect screen.",
            handler = Kick
        };

        internal static void Kick(Player player, Command cmd) {
            string name = cmd.Next();
            if (name != null) {
                string msg = cmd.NextAll();
                Player offender = Server.FindPlayer(name);
                if (offender != null) {
                    if (!player.info.playerClass.CanKick(offender.info.playerClass)) {
                        player.Message("You can only kick players ranked " + player.info.playerClass.maxKick.color + player.info.playerClass.maxKick.name + Color.Sys + " or lower.");
                        player.Message(offender.GetLogName() + " is ranked " + offender.info.playerClass.name + ".");
                    } else {
                        Server.SendToAll(Color.Red + offender.nick + " was kicked by " + player.nick);
                        if (msg != null && msg.Length > 0) {
                            Logger.Log("{0} was kicked by {1}. Memo: {2}", LogType.UserActivity, offender.GetLogName(), player.GetLogName(), msg);
                            offender.session.Kick("Kicked by " + player.GetLogName() + ": " + msg);
                        } else {
                            Logger.Log("{0} was kicked by {1}", LogType.UserActivity, offender.GetLogName(), player.GetLogName());
                            offender.session.Kick("You have been kicked by " + player.GetLogName());
                        }
                    }
                } else {
                    player.NoPlayerMessage(name);
                }
            } else {
                player.Message("Usage: " + Color.Help + "/kick PlayerName [Message]" +
                                   Color.Sys + " or " + Color.Help + "/k PlayerName [Message]");
            }
        }

        #endregion

        #region Class

        static CommandDescriptor cdChangeClass = new CommandDescriptor {
            name = "user",
            aliases = new string[] { "rank", "promote", "demote" },
            consoleSafe = true,
            usage = "/user PlayerName ClassName",
            help = "Changes the class/rank of a player to a specified class.",
            handler = ChangeClass
        };

        internal static void ChangeClass(Player player, Command cmd) {
            string name = cmd.Next();
            string newClassName = cmd.Next();

            // Check arguments
            if (newClassName == null) {
                player.Message("Usage: " + Color.Help + "/user PlayerName ClassName");
                player.Message("To see a list of classes and permissions, use " + Color.Help + "/class");
                return;
            }

            // Parse class name
            PlayerClass newClass = ClassList.FindClass(newClassName);
            if (newClass == null) {
                player.Message("Unrecognized player class: " + newClassName);
                return;
            }

            // Parse player name
            PlayerInfo info;
            Player target = Server.FindPlayerExact(name);
            if (target == null) {
                info = PlayerDB.FindPlayerInfoExact(name);
            } else {
                info = target.info;
            }

            if (info == null) {
                info = PlayerDB.AddFakeEntry(name);
                player.Message("Note: \"" + name + "\" was not found in PlayerDB.");
            }

            DoChangeClass(player, info, target, newClass);
        }

        internal static void DoChangeClass(Player player, PlayerInfo targetInfo, Player target, PlayerClass newClass) {

            bool promote = (targetInfo.playerClass.rank < newClass.rank);
            string targetFullName = (target == null ? targetInfo.name : target.GetLogName());

            // Make sure it's not same rank
            if (targetInfo.playerClass == newClass) {
                player.Message(targetFullName + " is already " + newClass.color + newClass.name);
                return;
            }

            // Make sure player has the general permissions
            if ((promote && !player.Can(Permission.Promote))) {
                player.NoAccessMessage(Permission.Promote);
                return;
            } else if (!promote && !player.Can(Permission.Demote)) {
                player.NoAccessMessage(Permission.Demote);
                return;
            }

            // Make sure player has the specific permissions (including limits)
            if (promote && !player.info.playerClass.CanPromote(newClass)) {
                player.Message("You can only promote players up to " + player.info.playerClass.maxPromote.color + player.info.playerClass.maxPromote.name);
                player.Message(targetFullName + " is ranked " + targetInfo.playerClass.name + ".");
                return;
            } else if (!promote && !player.info.playerClass.CanDemote(targetInfo.playerClass)) {
                player.Message("You can only demote players that are " + player.info.playerClass.maxDemote.color + player.info.playerClass.maxDemote.name + Color.Sys + " or lower.");
                player.Message(targetFullName + " is ranked " + targetInfo.playerClass.name + ".");
                return;
            }

            // Do the class change
            if ((promote && targetInfo.playerClass.rank < newClass.rank) ||
                (!promote && targetInfo.playerClass.rank > newClass.rank)) {
                PlayerClass oldClass = targetInfo.playerClass;

                Logger.Log("{0} changed the class of {1} from {2} to {3}.", LogType.UserActivity,
                            player.GetLogName(), targetFullName, targetInfo.playerClass.name, newClass.name);
                targetInfo.playerClass = newClass;
                targetInfo.classChangeDate = DateTime.Now;
                targetInfo.classChangedBy = player.name;

                if (promote) {
                    player.Message("You promoted " + targetInfo.name + " to " + newClass.color + newClass.name);
                } else {
                    player.Message("You demoted " + targetInfo.name + " to " + newClass.color + newClass.name);
                }

                if (target != null) {
                    if (promote) {
                        target.Message("You have been promoted to " + newClass.color + newClass.name + Color.Sys + " by " + player.GetLogName());
                    } else {
                        target.Message("You have been demoted to " + newClass.color + newClass.name + Color.Sys + " by " + player.GetLogName());
                    }
                }
            } else {
                if (promote) {
                    player.Message(targetFullName + " is already same or lower rank than " + newClass.name);
                } else {
                    player.Message(targetFullName + " is already same or higher rank than " + newClass.name);
                }
            }
        }

        #endregion

        #region Items

        static CommandDescriptor cdItem = new CommandDescriptor {
            name = "item",
            consoleSafe = false,
            permissions = new Permission[] { Permission.SpawnItems },
            usage = "/item <name/id> [quantity]",
            help = "Gives you [quantity] blocks or items of type <name/id>.",
            handler = cmdItem
        };

        internal static void cmdItem(Player player, Command cmd) {
            string strType = cmd.Next();
            string strAmount = cmd.Next();
            short type = (short)Block.Undefined;
            byte amount = 1;

            if (strAmount == null)
                amount = 1;
            else {
                if (!byte.TryParse(strAmount, out amount)) {
                    player.Message(Color.Announcement + "You must enter a valid amount (0-255) or leave the amount blank");
                    return;
                }
            }
            if (strType == null) {
                player.Message(Color.Announcement + "You must give either the name of the item or its ID number");
                return;
            }

            foreach (Block block in Enum.GetValues(typeof(Block))) {
                if (block.ToString().ToLower() != strType.ToLower())
                    continue;
                type = (byte)block;
                break;
            }

            if (type == (byte)Block.Undefined) {
                if (!short.TryParse(strType, out type)) {
                    player.Message(Color.Announcement + "You must give either the name of the item or its ID number");
                    return;
                }
            }

            player.Send(PacketWriter.MakeInventoryAddition(type, amount, (short)(type > 255 ? 1 : 0)));
            player.Message(Color.Announcement + "You have been given " + amount + " " + (type > 255 ? "of item " + type : ((Block)type).ToString()));
        }

        #endregion

        #region Whitelist

        static CommandDescriptor cdAddToWhitelist = new CommandDescriptor {
            name = "whitelist",
            consoleSafe = true,
            usage = "/whitelist <command>",
            help = "COMMANDS: add <name>",
            handler = cmdAddToWhitelist
        };

        internal static void cmdAddToWhitelist(Player player, Command cmd) {
            string command = cmd.Next();
            string param = cmd.Next();

            switch (command) {
                case "add":
                    if (param == null) {
                        player.Message(Color.Announcement + "You must specify a name to add to the whitelist!");
                        return;

                    } else if (Server.whitelist.Contains(param)) {
                        player.Message(Color.Announcement + "The user \"" + param + "\" is already on the whitelist!");
                        return;
                    }

                    Server.AddToWhitelist(param);
                    Server.LoadWhitelist();

                    player.Message(Color.Announcement + "The user \"" + param + "\" has been whitelisted.");
                    break;

                default:
                    player.Message(Color.Announcement + "Unknown whitelist command: " + command);
                    break;
            }
        }

        #endregion
    }
}
